﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ModelLayer;
using DBLayer;

namespace CtrLayer
{
    /// <summary>
    /// Class used in dijkstra algorithm
    /// </summary>
    public class Vertex 
    {
        public Airport Self { get; set; }
        public bool Visited { get; set; }
        public double Cost { get; set; }
        public Vertex Previous { get; set; }
        public TimeSpan CostDate { get; set; }
        public Vertex() 
        {
            Visited = false;
        }   
    }
    /// <summary>
    /// Class used to calculate shortest path with lowest cost and shortest flytime
    /// </summary>
    public class Dijkstra
    {
        private AirlineCtr airlineCtr;
        private BookingCtr bookingCtr;
        private List<Vertex> myVertex;
        private List<Flight> flights;
        public Dijkstra()
        {
            airlineCtr = new AirlineCtr();
            bookingCtr = new BookingCtr();
            myVertex = new List<Vertex>();

            Initialization();

        }
        /// <summary>
        /// Initialize and fills myvertex 
        /// </summary>
        private void Initialization()
        {
            List<Airport> airports;
            airports = airlineCtr.SelectAllAirports();
            flights = airlineCtr.SelectAllFlights();
            foreach(Airport air in airports)
            {
                Vertex newVertex = new Vertex();
                string id = air.AirportCode;
                newVertex.Self = airlineCtr.FindAirportByCode(id);
                myVertex.Add(newVertex);
            }
        }
        //Used to determine if an airport is adjacent. Used for calculating the fastest route only.
        private bool IsAdjacentNow(Vertex _startVertex, Vertex _endVertex, DateTime _flightDate)
        {
            return bookingCtr.HasValidConnection(_startVertex.Self, _endVertex.Self, _flightDate);
        }
        //Used to determine if an airport is adjacent. Used for calculating the cheapest route only.
        private bool IsAdjacent(Vertex _startVertex, Vertex _endVertex)
        {
            return airlineCtr.HasConnection(_startVertex.Self, _endVertex.Self);
        }
        //Used to get a list of connected vertex's for the calculation of the cheapest route.
        private List<Vertex>GetAdjacencies(Vertex _vertex)
        {
            List<Vertex> retList = new List<Vertex>();
            
            foreach(Vertex v in myVertex )
            {
                
                if(IsAdjacent(_vertex, v))
                {
                    retList.Add(v);
                }
            }
            return retList;
        
        }
        //Used to get a list of connected vertex's for the calculation of the fastest route.
        private List<Vertex>GetAdjacenciesNow(Vertex _vertex, DateTime _flighttime)
        {
            List<Vertex> retList = new List<Vertex>();
            
            foreach(Vertex v in myVertex )
            {
                
                if(IsAdjacentNow(_vertex, v, _flighttime))
                {
                    retList.Add(v);
                }
            }
            return retList;
        }
        private void UnMark()
        {
            foreach(Vertex v in myVertex)
            {
                v.Visited = false;
            }
        }
        private void SetCostToNull()
        {
            foreach (Vertex v in myVertex)
            {
                v.Cost = 0;
            }
        }
        private Vertex GetVertex(Airport _airport)
        {
            Vertex result = null;
                bool found = false;

            while(!found)
            {
                foreach(Vertex v in myVertex)
                {
                    if(v.Self.Equals(_airport))
                    {
                        result = v;
                        found = true;
                        break;
                    }
                }
            }
            return result;
        }
        //calculate price between airports
        private double GetPrice(Vertex _start, Vertex _end)
        {
            double price = 0;
            Flight flight = airlineCtr.FindFlightByAirports(_start.Self, _end.Self);
            price = flight.Price;

            return price;
        }
        //Get time
        private TimeSpan GetTime(Vertex _start,  Vertex _end)
        {
            TimeSpan price = new TimeSpan();
            price = _start.CostDate + _end.CostDate;
            return price;
        }
        //Find cheapest vertices on the grid 
        private List<Vertex> DijkstraPrice(Airport _startAirport, Airport _endAirport)
        {
            UnMark();
            SetCostToNull();
            Vertex startVertex = GetVertex(_startAirport);
            Vertex endVertex = GetVertex(_endAirport);
            
            Vertex Current = startVertex;
            List<Vertex> l = new List<Vertex>();
            l.Add(Current);

            while (l.Count > 0)
            {
                Current.Visited = true;
                l.Remove(Current);
                foreach (Vertex u in GetAdjacencies(Current))
                {
                    double weight = GetPrice(Current, u);
                    if (!u.Visited && u.Cost > weight + Current.Cost || u.Cost == 0 && u == startVertex || !u.Visited)
                    {
                        u.Cost = Current.Cost + weight;

                        u.Previous = Current;

                        if (!l.Contains(u))
                        {
                            l.Add(u);
                        }
                    }
                }
                if (l.Count > 0)
                {
                    Current = l[0];
                    foreach (Vertex v in l)
                    {
                        if (v.Cost < Current.Cost)
                        {
                            Current = v;
                        }
                    }
                }
                else if (!endVertex.Visited)
                {
                    return null;
                }
                if (endVertex.Previous != null)
                {
                    List<Vertex> list = new List<Vertex>();
                    return BackTrack(startVertex, endVertex, list);
                }
            }
            if (l != null)
            {
                return l;
            }
            else
            {
                return null;
            }
        }
        //Find farstes vertices on the grid
        private List<Vertex> DijkstraTime(Airport _startAirport, Airport _endAirport, DateTime _date)
        {
            UnMark();
            Vertex startVertex = GetVertex(_startAirport);
            Vertex endVertex = GetVertex(_endAirport);

            Vertex Current = startVertex;
            //Current.ArrivalDate = _date;
            List<Vertex> l = new List<Vertex>();
            l.Add(Current);

            while (l.Count > 0)
            {
                Current.Visited = true;
                l.Remove(Current);
                foreach (Vertex u in GetAdjacenciesNow(Current, _date))
                {
                    //TimeSpan weight = new TimeSpan();
                    TimeSpan weight = GetTime(Current, u);
                    DateTime tempDate = new DateTime();
                    if (!u.Visited && u.CostDate > Current.CostDate.Add(weight) || u.CostDate.Equals(tempDate) && u == startVertex || !u.Visited)
                    {
                        u.CostDate = Current.CostDate.Add(weight);

                        u.Previous = Current;

                        if (!l.Contains(u))
                        {
                            l.Add(u);
                        }
                    }
                }
                if (l.Count > 0)
                {
                    Current = l[0];
                    foreach (Vertex v in l)
                    {
                        if (v.CostDate < Current.CostDate)
                        {
                            Current = v;
                        }
                    }
                }
                else if (!endVertex.Visited)
                {
                    return null;
                }
                if (endVertex.Previous != null)
                {
                    List<Vertex> list = new List<Vertex>();
                    return BackTrack(startVertex, endVertex, list);
                }

                UnMark();
            }
            if (l != null)
            {
                return l;
            }
            else
            {
                return null;
            }
        }
        //Goes recursively through the visited vertices and return it in a respected list.
        private List<Vertex> BackTrack(Vertex _startVertex, Vertex _vertex, List<Vertex> _list)
        {
            _list.Add(_vertex);
            if (!_vertex.Equals(_startVertex))
            {
                BackTrack(_startVertex, _vertex.Previous, _list);
            }
            return _list;
        }
        //Uses Dijkstra's algoritm to calculate the cheapest route.
        public List<FlightInstance> GetCheapestRoute(Airport _startAirport, Airport _endAirport, DateTime _flyDate)
        {

            List<Vertex> _list = DijkstraPrice(_startAirport, _endAirport);
            //Buffer for landing and departure time.
            double buffer = 1;
            List<Flight> flights = new List<Flight>();
            List<FlightInstance> result = new List<FlightInstance>();
            while (_list.Count > 1)
            {
                Airport startAirport = _list[_list.Count - 1].Self;
                Airport endAirport = _list[_list.Count - 2].Self;
                Flight flight = airlineCtr.FindFlightByAirports(startAirport, endAirport);
                flights.Add(flight);
                _list.RemoveAt(_list.Count - 1);
            }
            foreach(Flight f in flights)
            {

                    FlightInstance ins = bookingCtr.ValidNextLink(f, _flyDate);
                    _flyDate = ins.ArrivalTime.AddHours(buffer);

                    result.Add(ins);

            }
            return result;

        }
        //Attempt to calculcate the fastet path through the graph.
        public List<FlightInstance> GetQuickestRoute(Airport _startAirport, Airport _endAirport, DateTime _flyDate)
        {

            List<Vertex> _list = DijkstraTime(_startAirport, _endAirport, _flyDate);
            //Buffer for landing and departure time.
            double buffer = 1;
            List<Flight> flights = new List<Flight>();
            List<FlightInstance> result = new List<FlightInstance>();
            while (_list.Count > 1)
            {
                Airport startAirport = _list[_list.Count - 1].Self;
                Airport endAirport = _list[_list.Count - 2].Self;
                Flight flight = airlineCtr.FindFlightByAirports(startAirport, endAirport);
                flights.Add(flight);
                _list.RemoveAt(_list.Count - 1);
            }
            foreach (Flight f in flights)
            {
                FlightInstance ins = bookingCtr.ValidNextLink(f, _flyDate);
                _flyDate = ins.ArrivalTime.AddHours(buffer);
                result.Add(ins);

            }
            return result;
        }
    }

}

